package com.tianji.learning.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;

import com.tianji.common.autoconfigure.mq.RabbitMqHelper;
import com.tianji.common.constants.MqConstants;
import com.tianji.common.exceptions.BizIllegalException;
import com.tianji.common.utils.BooleanUtils;
import com.tianji.common.utils.CollUtils;
import com.tianji.common.utils.DateUtils;
import com.tianji.common.utils.UserContext;
import com.tianji.learning.constant.RedisConstants;
import com.tianji.learning.domain.po.SignRecord;
import com.tianji.learning.domain.vo.SignResultVO;
import com.tianji.learning.mapper.SignRecordMapper;
import com.tianji.learning.mq.message.SignInMessage;
import com.tianji.learning.service.SignRecordService;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.connection.BitFieldSubCommands;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.util.List;

/**
 * @author Cammy
 * @description 针对表【sign_record(签到记录表)】的数据库操作Service实现
 * @createDate 2025-02-24 03:14:03
 */
@Service
@RequiredArgsConstructor
public class SignRecordServiceImpl extends ServiceImpl<SignRecordMapper, SignRecord>
        implements SignRecordService {

    private final StringRedisTemplate redisTemplate;
    private final RabbitMqHelper rabbitMqHelper;

    /**
     * 签到功能接口
     * @return
     */
    @Override
    public SignResultVO addSignRecords() {
        // 1.签到
        // 1.1 获取登录用户
        Long userId = UserContext.getUser();
        // 1.2 获取日期
        LocalDate now = LocalDate.now();
        // 1.3 拼接key
        String key = RedisConstants.SIGN_RECORD_KEY_PREFIX + userId + now.format(DateUtils.SIGN_DATE_SUFFIX_FORMATTER);
        // 1.4 计算key
        int offset = now.getDayOfMonth() - 1;
        // 1.5 保存签到信息
        Boolean exists = redisTemplate.opsForValue().setBit(key, offset, true);
        if (BooleanUtils.isTrue(exists)) {
            throw new BizIllegalException("不允许重复签到！");
        }
        // 2.计算连续签到天数
        int signDays = this.countSignDays(key, now.getDayOfMonth());
        // 3.计算签到积分
        int rewardPoints = 0;
        switch (signDays) {
            case 7:
                rewardPoints = 10;
                break;
            case 14:
                rewardPoints = 20;
                break;
            case 28:
                rewardPoints = 40;
                break;
        }
        // 4.保存积分明细表
        rabbitMqHelper.send(
                MqConstants.Exchange.LEARNING_EXCHANGE,
                MqConstants.Key.SIGN_IN,
                SignInMessage.of(userId, rewardPoints + 1) // 签到积分是 基本得分+奖励积分
        );

        // 5.封装返回
        SignResultVO signResultVO = new SignResultVO();
        signResultVO.setSignPoints(signDays);
        signResultVO.setRewardPoints(rewardPoints);
        return signResultVO;
    }

    /**
     * todo——查询签到记录
     * @return
     */
    @Override
    public Byte[] querySignRecords() {
        // 1.获取登录用户
        Long userId = UserContext.getUser();
        // 2.获取日期
        LocalDate now = LocalDate.now();
        int dayOfMonth = now.getDayOfMonth();
        // 3.拼接key
        String key = RedisConstants.SIGN_RECORD_KEY_PREFIX
                + userId
                + now.format(DateUtils.SIGN_DATE_SUFFIX_FORMATTER);
        // 4.读取
        List<Long> result = redisTemplate.opsForValue()
                .bitField(key, BitFieldSubCommands.create().get(
                        BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth)).valueAt(0));
        if (CollUtils.isEmpty(result)) {
            return new Byte[0];
        }
        int num = result.get(0).intValue();

        Byte[] arr = new Byte[dayOfMonth];
        int pos = dayOfMonth - 1;
        while (pos >= 0) {
            arr[pos--] = (byte) (num & 1);
            // 把数字右移一位，抛弃最后一个bit位，继续下一个bit位
            num >>>= 1;
        }
        return arr;
    }

    private int countSignDays(String key, int dayOfMonth) {
        /*
         * 把二进制数中的每一个bit位当做弹夹中的一个个的子弹。遍历bit位的过程，就是打空弹夹的过程：
         *      ● 将弹夹最上方的子弹上膛（找到最后一个bit位，并获取它）
         *      ● 发射子弹（移除这个bit位，下一个bit位就成为最后一个）
         *      ● 回到步骤1
         * 最终，每一发子弹都会从备发射，这就像每一个bit位都会被遍历。
         *
         * 111111111110011111
         * 11111111111001111-->1
         * 011111111111001111
         */
        // 1.获取本月第一天开始到今天位置的所有签到记录
        List<Long> result = redisTemplate.opsForValue()
                .bitField(key, BitFieldSubCommands.create().get(
                        BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth)
                ).valueAt(0));
        if (CollUtils.isEmpty(result)) {
            return 0;
        }
        int num = result.get(0).intValue();
        // 2.定义一个计数器
        int count = 0;
        // 3.循环，与1做运算，得到最后一个bit，判断是否为0，为0就终止，为1继续
        while ((num & 1) == 1) {
            // 4.计数器+1
            count++;
            // 5.把数字右移一位，最后以为被舍弃，倒数第二位成了最后一位
            num >>>= 1;
        }

        return count;
    }
}




